/*******************************************************************************
 * Copyright 2013 Naver Business Platform Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.iye.puretennis.pulltorefresh.configuration.xml;


import com.iye.puretennis.pulltorefresh.library.internal.Assert;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;

/**
 * {@code XmlPullNode} is used for parsing xml as a node tree.<br />
 * If you implement {@link com.iye.puretennis.pulltorefresh.configuration.xml.XmlPullNode#XmlPullNodeCallback} and pass that on to this class,<br /> {@link XmlPullNodeParser} calls {@link com.iye.puretennis.pulltorefresh.configuration.xml.XmlPullNode#XmlPullNodeCallback#process(org.xmlpull.v1.XmlPullParser)} when it has found this current node during parse.<br />
 * When it has found a child node of current node' children, and if the child node has been added in current node as child XmlPullNode, {@link XmlPullNodeParser} continues parsing at the child.
 *
 * @author Wonjun Kim
 */
class XmlPullNode {
    /**
     * Unlimited repeat value <br />
     * If you add a child (by calling {@link #addChildNode(com.iye.puretennis.pulltorefresh.configuration.xml.XmlPullNode, int)} with setting repeat limit and the limit is {@code INFINITE}, the limit becomes meaningless.<br />
     */
    public static final int INFINITE = -1;
    /**
     * Map which store children<br />
     * child's node name can have upper case or lower. all is the same whether that is upper or lower.
     */
    private final Map<String, XmlPullNodeContainer> children = new TreeMap<String, XmlPullNodeContainer>(
            String.CASE_INSENSITIVE_ORDER);
    /**
     * Current node's name
     */
    private final String tagName;
    /**
     * Callback <br /> {@code XmlPullNodeCallback.process(XmlPullParser)} method is called when this node has found in xml.
     */
    private final XmlPullNodeCallback callback;
    /**
     * Default callback. This doesn't any kind of action.
     */
    private static final XmlPullNodeCallback nullCallback = new XmlPullNodeCallback() {
        @Override
        public void process(XmlPullParser parser) {
            // do nothing
        }
    };

    /**
     * Default Constructor (only set a node name)
     *
     * @param tagName Node name. this must not be null
     */
    public XmlPullNode(String tagName) {
        this(tagName, null);
    }

    /**
     * Constructor with {@code tagName} and {@code callback}
     *
     * @param tagName  Node name. this must not be null
     * @param callback
     */
    public XmlPullNode(String tagName, XmlPullNodeCallback callback) {
        Assert.notNull(tagName, "Tag Name");
        this.tagName = tagName;
        this.callback = (callback == null) ? nullCallback : callback;
    }

    /**
     * @return Current node name
     */
    public String getName() {
        return tagName;
    }

    /**
     * Add a child node into this node without repeat limit<br />
     * NOTE: When it has found a child node of current node' children, and if the child node has been added in current node as child XmlPullNode, {@link XmlPullNodeParser} continues parsing at the child.
     *
     * @param node Child node to add
     * @return true if it is successful to add
     */
    public boolean addChildNode(XmlPullNode node) {
        return addChildNode(node, INFINITE);
    }

    /**
     * Add a child node into this node with repeat limit<br />
     * NOTE: When it has found a child node of current node' children, and if the child node has been added in current node as child XmlPullNode, {@link XmlPullNodeParser} continues parsing at the child.
     *
     * @param node        Child node to add
     * @param repeatLimit Repeat limit. if a child repeats over the limit, an error occurs during parse.
     * @return true if it is successful to add
     */
    public boolean addChildNode(XmlPullNode node, int repeatLimit) {
        XmlPullNodeContainer pullNodeContainer = children.get(node.getName());
        if (pullNodeContainer != null) {
            return false;
        }

        children.put(node.getName(),
                new XmlPullNodeContainer(node, repeatLimit));
        return true;
    }

    /**
     * Get a child of this node's chidren
     *
     * @param tagName Child node name to find
     * @return Child node if the child has been found or null
     * @throws org.xmlpull.v1.XmlPullParserException
     */
    public XmlPullNode getChild(String tagName) throws XmlPullParserException {
        XmlPullNodeContainer pullNodeContainer = children.get(tagName);
        if (pullNodeContainer == null) {
            return null;
        }
        return pullNodeContainer.takeXmlPullNode();

    }

    /**
     * @return Callback instance
     */
    public XmlPullNodeCallback getCallback() {
        return callback;
    }

    /**
     * Callback to process some action for {@code XmlPullNode}. <br />
     * <p/>
     * NOTE: If you implement {@link com.iye.puretennis.pulltorefresh.configuration.xml.XmlPullNode#XmlPullNodeCallback} and pass that on to {@code XmlPullNode} class,<br /> {@link XmlPullNodeParser} calls {@link com.iye.puretennis.pulltorefresh.configuration.xml.XmlPullNode#XmlPullNodeCallback#process(org.xmlpull.v1.XmlPullParser)} when it has found this the node during parse.
     *
     * @author Wonjun Kim
     */
    public static interface XmlPullNodeCallback {
        /**
         * @param parser {@code XmlPullParser} instance which are parsing xml. WARNING: Must carefully use {@code parser}. using {@code parser} can affect to cause an parsing error. Because {@code parser} can easily go to some wrong position during usage.
         * @throws org.xmlpull.v1.XmlPullParserException
         * @throws java.io.IOException
         */
        void process(XmlPullParser parser) throws XmlPullParserException, IOException;
    }

    /**
     * {@XmlPullNodeContainer} is a container for saving {@code repeatLimit}. And check the limit to occur an error. <br />
     * NOTE : Justly, this Class is not thread-safe. :)
     *
     * @author Wonjun Kim
     */
    private static class XmlPullNodeContainer {
        /**
         * Real {@code XmlPullnode} instance
         */
        private XmlPullNode node;
        /**
         * Repetition limit value decreasing one by which a node occurs at a time
         */
        private int repeatLimit;

        /**
         * Default Constructor
         *
         * @param node Infinite repeat {@code XmlPullNode} instance
         */
        public XmlPullNodeContainer(XmlPullNode node) {
            this(node, INFINITE);
        }

        /**
         * Constructor with {@code node} and {@code repeatLimit}
         *
         * @param node        Finite repeat (only if you set positive {@code repeatLimit}) {@code XmlPullNode} instance
         * @param repeatLimit Repeat limit value. This must be positive.
         */
        public XmlPullNodeContainer(XmlPullNode node, int repeatLimit) {
            Assert.notNull(node, "XmlPullNode");
            this.node = node;
            this.repeatLimit = repeatLimit;
        }

        /**
         * Return {@code XmlPullNode} instance or throw a limit error
         *
         * @return {@code XmlPullNode} instance if the node repeats under the limit
         * @throws org.xmlpull.v1.XmlPullParserException if a number of repetitions reaches the limit
         */
        public XmlPullNode takeXmlPullNode() throws XmlPullParserException {
            if (repeatLimit > 0) {
                decreaseRepeatLimit();
                return node;
            }

            // throw an error
            if (repeatLimit == 0) {
                String tagName = node.getName();
                throw new XmlPullParserException("Tag '" + tagName
                        + "' should not have more " + repeatLimit + " nodes.");
            }

            // if infinite repeats,
            return node;

        }

        /**
         * one step forward to the limit error!
         */
        private void decreaseRepeatLimit() {
            --repeatLimit;
        }
    }
}
